//
//  IQBilibiliManager.m
//  IQWinner
//
//  Created by yoolooo on 2022/5/7.
//

#import "IQBilibiliManager.h"
#import <SocketRocket/SRWebSocket.h>
#import <zlib.h>


#define kBiliWSURL @"ws://broadcastlv.chat.bilibili.com:2244/sub"

typedef NS_ENUM(NSUInteger,IQBilibiliOperation){
    // 客户端发送心跳值
    WS_OP_HEARTBEAT = 2,
    // 服务端返回心跳值
    WS_OP_HEARTBEAT_REPLY = 3,
    // 返回消息
    WS_OP_MESSAGE = 5,
    // 用户授权加入房间
    WS_OP_USER_AUTHENTICATION = 7,
    // 建立连接成功，客户端接收到此信息时需要返回一个心跳包
    WS_OP_CONNECT_SUCCESS = 8,
    // Header Length
    WS_PACKAGE_HEADER_TOTAL_LENGTH = 16,
    WS_PACKAGE_OFFSET = 0,
    WS_HEADER_OFFSET = 4,
    WS_VERSION_OFFSET = 6,
    WS_OPERATION_OFFSET = 8,
    WS_SEQUENCE_OFFSET = 12,
    WS_BODY_PROTOCOL_VERSION_NORMAL = 0,
    // deflate 压缩版本
    WS_BODY_PROTOCOL_VERSION_DEFLATE2,
    WS_HEADER_DEFAULT_VERSION = 1,
    WS_HEADER_DEFAULT_OPERATION = 1,
    WS_HEADER_DEFAULT_SEQUENCE = 1,
    WS_AUTH_OK = 0,
    WS_AUTH_TOKEN_ERROR = -101
};

typedef NS_ENUM(NSUInteger,IQBilibiliVersion) {
    IQBilibiliVersionJson = 0,
    IQBilibiliVersionInt32 = 1,
    IQBilibiliVersionZlib = 2,
    IQBilibiliVersionBrotli = 3,
};



static IQBilibiliManager *manager = nil;

@interface IQBilibiliManager ()<SRWebSocketDelegate>

@property (nonatomic, strong) SRWebSocket *ws;
@property(nonatomic, assign) NSUInteger roomID;
@property(nonatomic, assign) NSUInteger uid;

@end

@implementation IQBilibiliManager

+ (instancetype)shared{
    if (!manager) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            manager = [[IQBilibiliManager alloc] init];
        });
    }
    return manager;
}

- (instancetype)init{
    if (self = [super init]) {
        _uid = 0;
        _roomID = 0;
    }
    return self;
}

- (void)startConnentWithRoomID:(NSUInteger)roomID uid:(NSUInteger)uid{
    self.roomID = roomID;
    NSURL *url = [NSURL URLWithString:kBiliWSURL];
    self.ws = [[SRWebSocket alloc] initWithURL:url];
    self.ws.delegate = self;
    [self.ws open];

    [NSTimer scheduledTimerWithTimeInterval:30 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [self sendHeartPackage];
    }];
}

- (void)sendHeartPackage{
    [self.ws sendData:[self converMessage:nil operation:WS_OP_HEARTBEAT] error:nil];
}

- (void)sendAuthMassage{
    NSDictionary *auth_params = @{@"uid":@(self.uid),@"roomid":@(self.roomID),@"protover":@(2),@"platform":@"web",@"clientver":@"1.4.0"};
    NSError *error;
    NSData *data = [NSJSONSerialization dataWithJSONObject:auth_params options:NSJSONWritingPrettyPrinted error:&error];
    if (!error) {
        data = [NSJSONSerialization dataWithJSONObject:auth_params options:NSJSONWritingPrettyPrinted error:&error];
        [self.ws sendData:[self converMessage:data operation:WS_OP_USER_AUTHENTICATION] error:&error];
        if (error) {
            NSLog(@"error:%@",error);
        }
    }
}


#pragma mark - SRWebSocketDelegate
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{

}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string{
    NSLog(@"%s",__func__);
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data{
    [self handleReciveMessageData:data];
}


- (void)webSocketDidOpen:(SRWebSocket *)webSocket{
    NSLog(@"%s",__func__);
    [self sendAuthMassage];
}


- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"%s error:%@",__func__,error);
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean{
    NSLog(@"%s %@",__func__,reason);
}

- (void)webSocket:(SRWebSocket *)webSocket didReceivePingWithData:(nullable NSData *)data{
    NSLog(@"%s",__func__);
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(nullable NSData *)pongData{
    NSLog(@"didReceivePong");
}

- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket{
    return YES;
}





- (NSData*)converMessage:(nullable NSData *)data operation:(NSUInteger)operation{

    NSUInteger dataLength = data ? data.length : 0;
    NSUInteger headerLength = 16;

    char *bytes = NULL;
    NSUInteger index = 0;
    NSUInteger msgLength = headerLength + dataLength;
    bytes = (char *)malloc(msgLength);
    memset(bytes, 0, msgLength);
    bytes[index++] = msgLength & 0xff000000;
    bytes[index++] = msgLength & 0x00ff0000;
    bytes[index++] = msgLength & 0x0000ff00;
    bytes[index++] = msgLength & 0x000000ff;


    bytes[index++] = headerLength & 0xff00;
    bytes[index++] = headerLength & 0x00ff;

    bytes[index++] = 1 & 0xff00;
    bytes[index++] = 1 & 0x00ff;

    bytes[index++] = operation & 0xff000000;
    bytes[index++] = operation & 0x00ff0000;
    bytes[index++] = operation & 0x0000ff00;
    bytes[index++] = operation & 0x000000ff;


    bytes[index++] = 1 & 0xff000000;
    bytes[index++] = 1 & 0x00ff0000;
    bytes[index++] = 1 & 0x0000ff00;
    bytes[index++] = 1 & 0x000000ff;
    if (data) {
        memcpy(&bytes[index], data.bytes, msgLength);
    }

    NSData *resData = [NSData dataWithBytes:bytes length:msgLength];
    free(bytes);
    return resData;
}

- (void)handleReciveMessageData:(NSData *)data{

    const char *bytes = data.bytes;
    NSInteger length = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
    NSInteger headerLength = (bytes[4] << 8) | bytes[5];

    NSInteger version = bytes[6] << 8 | bytes[7];
    NSInteger op = (bytes[8] << 24) | (bytes[9] << 16) | (bytes[10] << 8) | bytes[11];;

    NSLog(@"总长度：%ld-%ld header:%ld version:%ld op:%ld",data.length,length,headerLength,version,op);
    if (data.length > length & length > 0) {
        NSData *otherData = [data subdataWithRange:NSMakeRange(length, data.length - length)];
        [self handleReciveMessageData:otherData];
        data = [data subdataWithRange:NSMakeRange(0, length)];
    }
    if (data.length > 16) {
        NSData *messageData = [data subdataWithRange:NSMakeRange(16, data.length-16)];
        if (version == 1){
            NSString *msg = [[NSString alloc] initWithData:messageData encoding:NSUTF8StringEncoding];
            NSLog(@"handleJsonMessage:%@",msg);
        }else if (version == 2) {
            NSData *unzipMessageData = [self decompress:messageData];
            [self handleReciveMessageData:unzipMessageData];
//            NSString *msg = [[NSString alloc] initWithData:unzipMessageData encoding:NSUTF8StringEncoding];
//            NSLog(@"handleMessage:%ld,%@",unzipMessageData.length,msg);
        }else if(version == 0){
            NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:messageData options:kNilOptions error:nil];
            switch (op) {
                case WS_OP_MESSAGE:
                    [self handlerMessageOperation:dic];
                    break;

                default:
                    break;
            }
        }
    }
}


- (void)handlerMessageOperation:(NSDictionary *)messageDict{
    NSString *cmd = [messageDict objectForKey:@"cmd"];
    if ([cmd isEqualToString:@"DANMU_MSG"]) {
        [self handleDanmu:messageDict];
    }else if([cmd isEqualToString:@"SEND_GIFT"]){
        [self handleGift:messageDict];
    }else if([cmd isEqualToString:@"INTERACT_WORD"]){
        [self handleInteract:messageDict];
    }
    else{
        NSLog(@"handleBolbMessage:%@",messageDict);
    }
}

- (void)handleDanmu:(NSDictionary*)danmuDict{
    NSArray *infoArray = danmuDict[@"info"];
    if (![infoArray isKindOfClass:[NSArray class]]) { return; }

    NSString *content = infoArray[1];
    NSArray *senderInfo = infoArray[2];
    NSUInteger senderUid = [senderInfo[0] integerValue];
    NSString *senderName = senderInfo[1];
    NSLog(@"###收到弹幕消息 %@:%@",senderName,content);

    if (self.filter) {
        BOOL rs = self.filter(content);
        if (rs) {
            if(self.IQBilibiliReceiveMessage) self.IQBilibiliReceiveMessage(content);
        }
    }
}

- (void)handleGift:(NSDictionary *)giftDict{

}
- (void)handleInteract:(NSDictionary *)dict{
    NSDictionary *dataDict = dict[@"data"];
    NSInteger uid = [dataDict[@"uid"] integerValue];
    NSString *uname = dataDict[@"uname"];

    NSLog(@"###进入房间:uid:%ld name:%@",uid,uname);
}





#pragma mark - Util
/*
@param data 需要解压数据
@return 已经解压的数据
*/
- (NSData *)decompress:(NSData *)data {

   if ([data length] == 0) return data;
   // 初始化 z_stream
   z_stream strm;
   strm.zalloc = Z_NULL;
   strm.zfree = Z_NULL;
   strm.opaque = Z_NULL;
   strm.total_out = 0;
   strm.next_in = (Bytef *)[data bytes];
   strm.avail_in = (int)[data length];

   unsigned full_length = (int)[data length];
   unsigned half_length = (int)[data length] / 2;
   NSMutableData * decompressed = [[NSMutableData alloc]
                                   initWithLength: full_length * 2];
   BOOL done = false;
   int status;

   if (inflateInit2(&strm,47) != Z_OK){
       return nil;
   }
   while (!done) {
       if (strm.total_out >= [decompressed length])
           [decompressed increaseLengthBy: half_length];
       strm.next_out = [decompressed mutableBytes] + strm.total_out;
       strm.avail_out = (uint)[decompressed length] - (uint)strm.total_out;

       status = inflate(&strm, Z_SYNC_FLUSH);
       if (status == Z_STREAM_END)
           done = YES;
       else if (status != Z_OK)
           break;
   }
    if (inflateEnd(&strm) != Z_OK) {
        return nil;
    }
   if (done) {
       [decompressed setLength: strm.total_out];
       return [NSData dataWithData:decompressed];
   }
   return nil;
}


@end
